Unique pointers | 独一无二的指针(所有权指针)
原文: https://github.com/nrc/r4cppp/blob/master/unique.md 翻译者: Scott Huang 翻译日期: Agust 25, 2015 于厦门
Rust是一种系统级的语言,因此必须给您访问原始内存的能力。它通过指针(如在C++里)来实现。指针是Rust和C++有很大区别的地方之一,语法和语义上都有不同。Rust通过指针类型检查来确保内存安全。这是它的相对其他语言的一个主要优点。虽然该类型系统有点复杂,但是您可以获得内存安全和裸金属性能。
我曾打算在一篇文章中涵盖所有的Rust的指针,但我认为这个主题太大。所以这篇文章将只包括一个独特的指针(unique pointers)—其他类型将在后续的博文中谈论。
第一,不用指针的例子:
fn foo() {
let x = 75;
// ... do something with `x` ... 操作`x`
}
当我们到达foo
函数末尾,x
超出范围(在Rust里和C++一样)。那意味着变量不可以再被访问,这个变量所占的内存可以被回收重复使用。
在Rust里,对每一个类型T
,我们可以写Box<T>
所有权(也叫独一的)指针来指向它。我们用Box::new(...)
来分配堆空间,并用给定的值来初始化这块空间。 这类似于c++语言的new
。
例如,
fn foo() {
let x = Box::new(75);
}
这里x
是一个指向一个堆位置的指针,其中包含的值是75。x
拥有类型Box<int>
;我们可以写成let x: Box<int> = Box::new(75);
。这类似于C++语言的int* x = new int(75);
。不像在C++,Rust会为我们整理内存,这样就不需要调用“释放”或“删除”了。独一的指针的行为类似于值-当变量超出范围时被删除。在我们的示例中,在函数foo
结束后,x
不能够被继续访问,并且x
指向的内存可以被重复使用。
所有权指针使用*
来解引用,和C++一样。例如,
fn foo() {
let x = Box::new(75);
println!("`x` points to {}", *x);
}
与Rust里的原始类型类似,所有权指针和和他所指向的数据默认是不变的。与C不同,你不能有一个可变的(独一的)指针来指向不变的数据,反之亦然。数据的可变性,跟从指针。 例如,
fn foo() {
let x = Box::new(75);
let y = Box::new(42);
// x = y; // Not allowed, x is immutable. 不允许,x是不变的
// *x = 43; // Not allowed, *x is immutable. 不允许, *x是不变的
let mut x = Box::new(75);
x = y; // OK, x is mutable. 可以,x是可变的
*x = 43; // OK, *x is mutable. 可以,*x是可变的
}
所有权指针可以从一个函数中返回,并继续存在。如果他们返回,然后他们的内存不会被释放,即,没有悬空的Rust指针。内存不会泄露。然而,它最终会超出范围,然后它将被释放。例如,
fn foo() -> Box<i32> {
let x = Box::new(75);
x
}
fn bar() {
let y = foo();
// ... use y ... 使用y
}
在这里,内存在foo
里面初始化,回到bar
。x
从foo
返回并存储在y
,所以不能删除。在“bar”结束后,“y”超出范围,然后内存被回收。
所有权指针是独一无二的(也称为线性),因为在任何时候只有一个(所有权)指针指向同一内存。这是通过移动语义实现的。当一个指针指向某个值时,任何先前的指针都不能继续访问。例如,
fn foo() {
let x = Box::new(75);
let y = x;
// x can no longer be accessed x 不再能被访问
// let z = *x; // Error. 错误
}
同样,如果一个所有权指针被传递到另一个函数或存储在字段,它不再可以被访问:
fn bar(y: Box<int>) {
}
fn foo() {
let x = Box::new(75);
bar(x);
// x can no longer be accessed x不再能被访问
// let z = *x; // Error. 错误
}
Rust的所有权指针类似C++的 std::unique_ptr
s。 在Rust里,如同在C++里,只能有一个所有权指针指向一个值,当指针超出范围时该值被删除。Rust的大部分检查都发生在静态时而不是在运行时。所以,在C++访问一个独一的指针,其值移动将导致一个运行时错误(因为它将是空的)。在Rust里,这产生一个编译时错误,保证在运行时不出错。
稍后我们会看到Rust有可能创建其他类型的指针指向一个独特指针的值。这是类似于C++。然而,在C++里,当你在运行时拥有一个指向已释放内存的指针将导致错误。但那不可能在Rust(我们会看到,当我们谈到Rust的其他指针类型时)发生。
如上面所示,所有权指针必须解引用才能使用他们的值。然而,方法调用会自动解引用,这样就不需要一个->
操作符或使用'*'的方法调用。用这种方法,Rust指针有点同时类似于C++指针和引用。例如,
fn bar(x: Box<Foo>, y: Box<Box<Box<Box<Foo>>>>) {
x.foo();
y.foo();
}
假设类型Foo
有方法foo()
,这些表达式都OK的。
用一个存在的值调用Box::new()
,它并不会获取引用,它拷贝值。所以,
fn foo() {
let x = 3;
let mut y = Box::new(x);
*y = 45;
println!("x is still {}", x);
}
在一般情况下,Rust具有移动而不是复制的语义(如上所见unique指针)。原始类型具有复制的语义,所以在上面的例子中,值3是复制的,但是对于更为复杂的值,它将被移动。我们将在后面进行更详细的讨论。
有时在编程时,我们需要一个以上的引用指向一个值。对于这个,Rust有借贷指针。我将在下一篇文章中包括这些。